// 全局变量
// ************************************************************
// Jquery 别名
const $ = jQuery

// 表格相关
const tab = $('#tab-summary')
let currentRowEle = null,
    currentHostCfg = {}

// 主机数据
let PingSummaries = [],
    RunningHostsSet = new Set([])

// echarts相关
const chartEle = document.getElementById('echarts-main'),
    chartAlertEle = document.getElementById('echarts-alert'),
    myChart = echarts.init(chartEle)

// 辅助函数
// ************************************************************
// 与服务器通信故障的告警，此告警不能被关闭
function showDisconnectAlarm() {
    layer.open({
        title: false,
        content: '与服务器通信故障，请尝试刷新此页面，或与管理员联系。',
        icon: 5,
        btn: false,
        closeBtn: 0,
        shadeClose: false,
    })
}

// 缓存图片
function preCacheImages() {
    const images = [
        '/assets/layer/theme/default/icon.png'
    ]

    const img = new Image()
    images.forEach(value => {
        img.src = value
    })
}

// 换算 rtt 的单位为 ms
function convRTT(rtt) {
    if (rtt < 0) return 'Timed out'
    else return parseFloat((rtt / 1000 / 1000).toFixed(2)) + ' 毫秒'
}

// DataTable
// ************************************************************
// 初始化表格
function initDataTable() {
    tab.DataTable({
        data: PingSummaries,
        iDisplayLength: 25,
        bStateSave: true,
        deferRender: true, // 延迟渲染
        aaSorting: [[0, 'asc']], // 默认排序
        aoColumnDefs: [
            {bSortable: false, aTargets: [9, 10]},
            {bSearchable: false, aTargets: [1, 2, 3, 4, 5, 6, 7, 8, 10]},
        ], // 设置不排序列
        columns: [
            {title: 'IP', data: 'ip'},
            {title: '成功次数', data: 'succeed_count'},
            {title: '失败次数', data: 'failed_count'},
            {
                title: '平均<abbr title="Round-Trip Time 的缩写，这里的意思是PING报文的往返传输的时间延迟。">RTT</abbr>', data: null,
                render: (data, type, row) => convRTT(row.avg_rtt)
            },
            {title: '最大RTT', data: null, render: (data, type, row,) => convRTT(row.max_rtt)},
            {title: '最小RTT', data: null, render: (data, type, row) => convRTT(row.min_rtt)},
            {
                title: '<abbr title="英文是 Mean Deviation(mdev)，它表示 RTT 偏离平均值的程度，这个值越大说明你的网速越不稳定">偏移</abbr>RTT',
                data: 'mdev_rtt', render: (data, type, row) => convRTT(row.mdev_rtt)
            },
            {title: '最后一次的RTT', data: null, render: (data, type, row) => convRTT(row.last_rtt)},
            {
                title: '最后一次失败的时间',
                data: null,
                render: (data, type, row) => {
                    const t = new Date(row.last_failed_at)
                    return t.getTime() > 0 ? t.toLocaleString() : ''
                }
            },
            {title: '备注（双击可修改）', data: 'comment', class: 'cell-comment'},
            {
                title: '操作', width: '140px', data: null, render: (data, type, row, meta) => {
                    const html1 = `<div class="btn-group btn-group-sm" role="group">
                              <button type="button" class="btn btn-outline-primary btn-edit-one">编辑</button>
                              <button type="button" class="btn btn-outline-danger btn-del-one">删除</button>
                              <button type="button" class="btn btn-outline-warning btn-stop-one">停止</button>
                            </div>`,
                        html2 = `<div class="btn-group btn-group-sm" role="group">
                              <button type="button" class="btn btn-outline-primary btn-edit-one">编辑</button>
                              <button type="button" class="btn btn-outline-danger btn-del-one">删除</button>
                              <button type="button" class="btn btn-outline-success btn-start-one">启动</button>
                            </div>`
                    return RunningHostsSet.has(row.ip) ? html1 : html2
                }
            }
        ],
        fnRowCallback: (nRow, aData) => {
            // 指定行颜色
            if (aData.last_rtt < 0) {
                $(nRow).children('td').eq(7).addClass('text-danger')
            } else {
                $(nRow).children('td').eq(7).removeClass('text-danger')
            }

            // 如果ping没有启动则IP地址颜色变灰
            if (!RunningHostsSet.has(aData.ip)) {
                $(nRow).children('td').eq(0).addClass('text-black-50')
            } else {
                $(nRow).children('td').eq(0).removeClass('text-black-50')
            }

            // 如果有成功记录，则可以点击IP地址打开历史记录模态框
            if (aData.succeed_count > 0) {
                $(nRow).children('td').eq(0).addClass('has-historic-records')
                $(nRow).children('td').eq(0).css('cursor', 'pointer')
            } else {
                $(nRow).children('td').eq(0).removeClass('has-historic-records')
                $(nRow).children('td').eq(0).css('cursor', 'default')
            }
        }
    })
}

// 更新row
function updateDtRow(ip) {
    const t = tab.get(0)
    for (let i = 1; i < t.rows.length; i++) {
        const row = t.rows[i],
            cell = row.cells[0], // 第0列上IP地址
            row_ip = cell.textContent

        if (ip === row_ip) {
            const dt = tab.DataTable(),
                data = dt.row(row).data()
            dt.row(row).data(data).draw(false)
        }
    }
}

// 更新row
function updateDtRowWithData(data) {
    const t = tab.get(0)
    for (let i = 1; i < t.rows.length; i++) {
        const row = t.rows[i],
            cell = row.cells[0], // 第0列上IP地址
            row_ip = cell.textContent

        if (data.ip === row_ip) {
            const dt = tab.DataTable()
            dt.row(row).data(data).draw()
        }
    }
}

// APIs
// ************************************************************
// 获取已经启动pinger的主机的IP地址
function fetchRunningHostsIPs() {
    fetch('/hosts/running/ip_addresses', {method: 'GET'})
        .then(res => {
            return res.json()
        })
        .then(data => {
            for (let ip of data.data) RunningHostsSet.add(ip)
        })
}

// 下载 PingSummaries
function fetchPingSummaries() {
    fetch('/pingers/summaries').then(res => res.json())
        .then(data => {
            PingSummaries = data.data
            initDataTable() // 初始化表格
        })
}

// 设置pinger状态
function setHostRunningStatus(ip, status) {
    fetch(`/hosts/is_running/${ip}?status=${status}`, {method: 'PATCH'})
        .then(() => {
            layer.msg(`已将 ${ip} 的监控设置为：${status}`)
            if (status) {
                RunningHostsSet.add(ip)
            } else {
                RunningHostsSet.delete(ip)
            }

            updateDtRow(ip) // 更新表格行
        })
}

// 添加主机
function addHost(ip, timeout, interval) {
    if (PingSummaries.length >= 50) {
        layer.msg(`${ip} 添加失败，已达到系统的最大数量限制`, {icon: 2})
        return
    }

    fetch('/hosts', {
        method: 'POST', headers: {"Content-Type": "application/json"},
        body: JSON.stringify({
            ip: ip,
            ping_timeout: parseInt(timeout) * 1000 * 1000, // 换算成 go 的 time.Duration 单位
            ping_interval_dur: parseInt(interval) * 1000 * 1000
        })
    }).then(res => res.json())
        .then(res => {
            if (res.data) {
                layer.msg(`添加 ${res.data.ip} 主机成功`, {icon: 1})

                const rowData = {
                    ip: ip,
                    succeed_count: 0,
                    failed_count: 0,
                    max_rtt: 0,
                    min_rtt: 0,
                    avg_rtt: 0,
                    mdev_rtt: 0,
                    last_rtt: 0,
                    last_failed_at: '',
                    comment: ''
                }
                PingSummaries.push(rowData)

                const dt = tab.DataTable()
                dt.row.add(rowData).draw(false)
            }
        })
}

// 删除主机
function delHost() {
    const dt = tab.DataTable(),
        rowData = dt.row(currentRowEle).data()
    fetch(`/hosts/${rowData.ip}`, {method: 'DELETE'})
        .then(() => {
                layer.msg('已删除主机')
                dt.row(currentRowEle).remove().draw(false)
            }
        )
}

// 打开编辑主机的框
function showEditHostForm() {
    const dt = tab.DataTable(),
        rowData = dt.row(currentRowEle).data()

    fetch(`/hosts/${rowData.ip}`, {method: 'GET'}).then(res => res.json())
        .then(res => {
            const form = $('#form-edit-host'),
                cfg = res.data

            form.find('#ipt-edit-ping-timeout').val(cfg.ping_timeout / 1000 / 1000)
            form.find('#ipt-edit-ping-interval').val(cfg.ping_interval_dur / 1000 / 1000)
            form.find('#ipt-edit-payload-size').val(cfg.ping_payload_size)
            currentHostCfg = cfg

            $('#modal-edit-host').modal('show')
        })
}

// 备注
function changeComment(comment) {
    const dt = tab.DataTable(),
        rowData = dt.row(currentRowEle).data()
    fetch(`/pingers/summaries/comment/${rowData.ip}?v=${comment}`, {method: 'PATCH'})
}

// 设置所有的主机的PING
function setAllHostsRunningStatus(status) {
    if (status) {
        PingSummaries.forEach(item => {
            if (!RunningHostsSet.has(item.ip)) {
                setTimeout(() => {
                    setHostRunningStatus(item.ip, true)
                }, Math.random() * 2000)
            }
        })
    } else {
        RunningHostsSet.forEach(ip => {
            setTimeout(() => {
                setHostRunningStatus(ip, false)
            }, Math.random() * 2000)
        })
    }
}

// 清零所有计数器
function clearAll() {
    let index;

    // 按下 ESC 关闭此弹出层
    $('body', document).on('keyup', (e) => {
        if (e.which === 27) layer.close(index)
    })

    index = layer.confirm('确定要清零所有计数器吗？', {icon: 3, title: '询问'},
        // 确认按钮
        () => {
            fetch('/pingers/summaries', {method: 'DELETE'})
                .then(layer.msg('已经清零所有计数器', {icon: 6}))

            $('body', document).off('keyup') // 取消事件绑定
        },
        // 取消按钮
        () => $('body', document).off('keyup')
    )
}


function showHistoricRecords(st, end) {
    const dt = tab.DataTable(),
        rowData = dt.row(currentRowEle).data()
    if (!rowData || !rowData.ip) return

    let t1 = st ? st : moment().startOf('day').toDate(),
        t2 = end ? end : moment().endOf('day').toDate()

    fetch(`/ping/records/count/${rowData.ip}?fromTime=${t1.toISOString()}&toTime=${t2.toISOString()}`).then(res => res.json())
        .then(res => {
            if (res.data > 200000) {
                $(chartEle).hide()
                $(chartAlertEle).show()
                $(chartAlertEle).html(`您选择的时间段有 <strong>${res.data}</strong> 条数据，超过了 20 万条无法显示，请尝试缩短时间段`)
            } else if (res.data < 10) {
                $(chartEle).hide()
                $(chartAlertEle).show()
                $(chartAlertEle).html(`您选择的时间段有 <strong>${res.data}</strong> 条数据，少于 10 条无法显示，请尝试延长时间段`)
            } else {
                $(chartEle).show()
                $(chartAlertEle).hide()

                myChart.clear()
                fetch(`/ping/records/${rowData.ip}?fromTime=${t1.toISOString()}&toTime=${t2.toISOString()}`).then(res => res.json())
                    .then(res => {
                        const data = res.data
                        myChart.setOption({
                            tooltip: {trigger: 'axis'},
                            xAxis: {
                                data: data.map(function (item) {
                                    return moment(item.created_at).format('YYYY/M/D H:m:s')
                                })
                            },
                            yAxis: {
                                type: 'value',
                                axisLabel: {
                                    formatter: '{value} ms'
                                },
                            },
                            dataZoom: [
                                {
                                    showDetail: false,
                                    startValue: moment(data[0].created_at).format('YYYY/M/D H:m:s')
                                }],
                            visualMap: {
                                show: false,
                                pieces: [
                                    {lt: 0, color: 'red'},
                                    {gt: 1000, color: '#ff9933'}
                                ],
                                outOfRange: {color: 'green'}
                            },
                            series: [
                                {
                                    type: 'line',
                                    smooth: 0.3,
                                    symbol: 'none',
                                    name: 'RTT',
                                    lineStyle: {width: 1},
                                    data: data.map(function (item) {
                                        if (item.rtt <= -1) return -1
                                        else return parseFloat((item.rtt / 1000 / 1000).toFixed(2))
                                    })
                                }
                            ]
                        })
                        myChart.resize()
                    })
            }
        })
}

// 连接 websocket，用于更新 PingSummaries
// ************************************************************
function connectWS() {
    const ws = new WebSocket(`ws://${location.host}/ws/pingers/summaries`)
    if (!ws) {
        layer.alert('你的浏览器不支持 WebSocket，表格将不会自动更新', {icon: 5, title: '警告'})
        return
    }

    ws.onopen = function () {
        layer.msg('已经建立 WebSocket 会话，页面会实时更新', {icon: 1})
        console.debug('opened websocket')
    }
    ws.onclose = function () {
        showDisconnectAlarm()
    }
    ws.onerror = function (e) {
        showDisconnectAlarm()
    }
    ws.onmessage = function (e) {
        let aData = JSON.parse(e.data)

        if (aData && aData.length) { // 更新数据
            for (let i = 0; i < PingSummaries.length; i++) {
                if (!aData.length) break
                for (let j = aData.length - 1; j >= 0; j--) {
                    if (PingSummaries[i].ip === aData[j].ip) {
                        PingSummaries[i] = aData[j]
                        updateDtRowWithData(aData[j]) // 更新表格
                        aData.splice(j, 1)
                    }
                }
            }
        }
    }
}

// 提交添加主机的表单
function submitAddHostForm() {
    const form = document.querySelector('#form-add-host')

    const timeout = form.querySelector('#ipt-ping-timeout').value,
        interval = form.querySelector('#ipt-ping-interval').value,
        addresses = form.querySelector('#ipt-ip-addresses').value

    addresses.split('\n').forEach(ip => {
        ip = ip.trim()
        if (ip) {
            setTimeout(() => {
                addHost(ip, timeout, interval)
            }, Math.random() * 3000)
        }
    })

    form.reset()
    $('#modal-add-host').modal('hide')
}

// 编辑
function submitEditHostForm() {
    const form = document.querySelector('#form-edit-host')

    currentHostCfg.ping_timeout = form.querySelector('#ipt-edit-ping-timeout').value * 1000 * 1000,
        currentHostCfg.ping_interval_dur = form.querySelector('#ipt-edit-ping-interval').value * 1000 * 1000,
        currentHostCfg.ping_payload_size = parseInt(form.querySelector('#ipt-edit-payload-size').value)

    fetch(`/hosts/${currentHostCfg.ip}`, {
        method: 'PUT', headers: {"Content-Type": "application/json"},
        body: JSON.stringify(currentHostCfg)
    })
        .then(res => res.json())
        .then(() => {
                layer.msg(`${currentHostCfg.ip} 的配置更新成功`, {icon: 1})
                form.reset()
                $('#modal-edit-host').modal('hide')
            }
        )
}

(function () {
    // 初始化数据
    fetchRunningHostsIPs()
    fetchPingSummaries()

    // 连接ws
    connectWS()

    // 缓存图片
    preCacheImages()

    // 绑定按钮事件
    $('#btn-start-all').on('click', function () {
        $(this).attr('disabled', 'disabled')
        setTimeout(() => $(this).removeAttr('disabled'), 10000)
        setAllHostsRunningStatus(true)
    })
    $('#btn-stop-all').on('click', function () {
        $(this).attr('disabled', 'disabled')
        setTimeout(() => $(this).removeAttr('disabled'), 10000)
        setAllHostsRunningStatus(false)
    })
    $('#btn-clear-all').on('click', clearAll)

    // 绑定事件
    window.addEventListener('resize', function () {
        myChart.resize()
    })

    $('#modal-records').on('shown.bs.modal', function (e) {
        // 初始化曲线
        showHistoricRecords()

        laydate.render({
            elem: '#datetime-range-ipt',
            type: 'datetime',
            range: '-',
            btns: ['now', 'confirm'],
            format: 'yyyy/M/d H:m', // laydate 的格式和 moment 的格式定义不一样。
            max: moment().endOf('day').format('YYYY/M/D H:m'),
            value: moment().startOf('day').format('YYYY/M/D H:m') + ' - ' + moment().endOf('day').format('YYYY/M/D H:m'),
            done: function (value, date, endDate) {
                const t1 = moment({
                        y: date.year,
                        M: date.month - 1,
                        d: date.date,
                        h: date.hours,
                        m: date.minutes,
                        s: date.seconds
                    }).toDate(),
                    t2 = moment({
                        y: endDate.year,
                        M: endDate.month - 1,
                        d: endDate.date,
                        h: endDate.hours,
                        m: endDate.minutes,
                        s: endDate.seconds
                    }).toDate()
                showHistoricRecords(t1, t2)
            }
        })
    })

    // ----------------- 绑定表格中的按钮的事件 -----------------
    // 删除行
    tab.on('click', '.btn-del-one', function () {
        let idx
        currentRowEle = $(this).parents('tr')
        const dt = tab.DataTable(),
            data = dt.row(currentRowEle).data()

        // 按下 ESC 关闭此弹出层
        $('body', document).on('keyup', (e) => {
            if (e.which === 27) layer.close(idx)
        })

        idx = layer.confirm(`确定要删除 ${data.ip} 主机吗？`, {title: "询问", icon: 3},
            () => {
                delHost()
                $('body', document).off('keyup') // 取消事件绑定
            },
            () => $('body', document).off('keyup'),
        )
    })

    // 启动ping
    tab.on('click', '.btn-start-one', function () {
        const dt = tab.DataTable(),
            row = $(this).parents('tr'),
            data = dt.row(row).data()

        $(this).attr('disabled', 'disabled')
        setTimeout(() => {
            $(this).removeAttr('disabled')
        }, 1000 * 10)

        setHostRunningStatus(data.ip, true)
    })

    // 停止ping
    tab.on('click', '.btn-stop-one', function () {
        const dt = tab.DataTable(),
            row = $(this).parents('tr'),
            data = dt.row(row).data()

        $(this).attr('disabled', 'disabled')
        setTimeout(() => {
            $(this).removeAttr('disabled')
        }, 1000 * 10)

        setHostRunningStatus(data.ip, false)
    })

    // 编辑行
    tab.on('click', '.btn-edit-one', function () {
        currentRowEle = $(this).parents('tr')
        showEditHostForm()
    })

    // 编辑备注
    tab.on('dblclick', 'td.cell-comment', function () {
        currentRowEle = $(this).parents('tr')

        layer.prompt(
            {title: '修改备注'},
            function (val, idx) {
                changeComment(val)
                layer.close(idx)
            }
        )
    })

    // 打开历史记录曲线
    tab.on('click', 'td.has-historic-records', function () {
        currentRowEle = $(this).parents('tr')
        $('#modal-records').modal('show')
    })
})();

